import asyncio
import tempfile312 as tempfile
from dataclasses import dataclass
from subprocess import CalledProcessError

from ofrak import Unpacker, Resource
from ofrak.core import (
    GenericBinary,
    FilesystemRoot,
    File,
    Folder,
    SpecialFileType,
    MagicDescriptionPattern,
)
from ofrak.model.component_model import ComponentExternalTool, ComponentConfig

_DEBUGFS = ComponentExternalTool(
    "debugfs", "https://e2fsprogs.sourceforge.net/", "-V", brew_package="e2fsprogs"
)


@dataclass
class ExtFilesystem(GenericBinary, FilesystemRoot):
    pass


@dataclass
class Ext2Filesystem(ExtFilesystem):
    """
    Linux EXT2 filesystem.
    """


@dataclass
class Ext3Filesystem(ExtFilesystem):
    """
    Linux EXT3 filesystem.
    """


@dataclass
class Ext4Filesystem(ExtFilesystem):
    """
    Linux EXT4 filesystem.
    """


class ExtUnpacker(Unpacker[None]):
    """
    Extracts files and directories from Linux Extended (EXT2/EXT3/EXT4) filesystems, the standard
    Linux filesystem family. These filesystems support full Unix permissions, symbolic links, hard
    links, and extended attributes. Use when analyzing disk images, partition dumps, or embedded
    system storage that uses EXT filesystems. Common in Linux-based embedded devices, development
    boards, and virtualized environments. Note that there are currently no EXT packers, so after
    unpacking, the resource cannot be repacked.
    """

    targets = (ExtFilesystem,)
    children = (File, Folder, SpecialFileType)
    external_dependencies = (_DEBUGFS,)

    async def unpack(self, resource: Resource, config: ComponentConfig = None) -> None:
        async with resource.temp_to_disk(suffix=".extfs") as temp_fs_path:
            with tempfile.TemporaryDirectory() as temp_dir:
                command = [
                    "debugfs",
                    "-R",
                    f"rdump / {temp_dir}",
                    temp_fs_path,
                ]
                proc = await asyncio.create_subprocess_exec(
                    *command,
                )
                returncode = await proc.wait()
                if returncode:
                    raise CalledProcessError(returncode=returncode, cmd=command)

                fs_view = await resource.view_as(ExtFilesystem)
                await fs_view.initialize_from_disk(temp_dir)


MagicDescriptionPattern.register(Ext2Filesystem, lambda s: "ext2 filesystem" in s.lower())
MagicDescriptionPattern.register(Ext3Filesystem, lambda s: "ext3 filesystem" in s.lower())
MagicDescriptionPattern.register(Ext4Filesystem, lambda s: "ext4 filesystem" in s.lower())
